package com.yuehuanghun.admin.generator.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.sql.DataSource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.base.Objects;
import com.jfinal.template.Engine;
import com.jfinal.template.Template;
import com.yuehuanghun.admin.core.config.Constants;
import com.yuehuanghun.admin.generator.config.GenConfig;
import com.yuehuanghun.admin.generator.config.GenConstants;
import com.yuehuanghun.admin.generator.domain.GenTable;
import com.yuehuanghun.admin.generator.domain.GenTableColumn;
import com.yuehuanghun.admin.generator.mapper.GenTableColumnMapper;
import com.yuehuanghun.admin.generator.mapper.GenTableMapper;
import com.yuehuanghun.admin.generator.model.Column;
import com.yuehuanghun.admin.generator.model.Table;
import com.yuehuanghun.admin.generator.model.TableFactory;
import com.yuehuanghun.admin.generator.util.EnjoyUtils;
import com.yuehuanghun.admin.generator.util.GenUtils;
import com.yuehuanghun.framework.exception.ValidateException;
import com.yuehuanghun.framework.json.JSON;
import com.yuehuanghun.framework.json.JSONObject;
import com.yuehuanghun.framework.util.DatabaseUtil;
import com.yuehuanghun.framework.util.StringUtils;
import com.yuehuanghun.framework.util.text.CharsetKit;
import com.yuehuanghun.mybatis.milu.data.Sort;
import com.yuehuanghun.mybatis.milu.ext.BaseServiceImpl;
import com.yuehuanghun.mybatis.milu.id.impl.snowflake.SnowflakeIdWorker;
import cn.hutool.core.date.DateUtil;

/**
 * 业务 服务层实现
 * 
 * @author yadmin
 */
@Service
public class GenTableServiceImpl extends BaseServiceImpl<GenTable, Long, GenTableMapper> implements IGenTableService {
	private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class);

	@Autowired
	private GenTableMapper genTableMapper;

	@Autowired
	private GenTableColumnMapper genTableColumnMapper;

	private DataSource dataSource;

	private Engine engine;

	public GenTableServiceImpl(DataSource dataSource) {
		this.dataSource = dataSource;
		engine = Engine.use();
		engine.setDevMode(true);
		engine.setToClassPathSourceFactory();
		engine.addSharedObject("snowflakeId", new SnowflakeIdWorker());
		engine.addSharedObject("_dbName", DatabaseUtil.getDataBase(dataSource).getDbName());
		engine.addSharedStaticMethod(StringUtils.class);
	}

	/**
	 * 查询业务信息
	 * 
	 * @param id 业务ID
	 * @return 业务信息
	 */
	@Override
	public GenTable selectGenTableById(Long id) {
		GenTable genTable = genTableMapper.findById(id).orElseThrow(() -> new ValidateException("构建表数据不存在：" + id));
		genTable.setColumns(genTableColumnMapper.findByLambdaCriteria(p -> p.eq(GenTableColumn::getTableId, id)));
		setTableFromOptions(genTable);
		return genTable;
	}

	/**
	 * 查询据库列表
	 * 
	 * @param genTable 业务信息
	 * @return 数据库表集合
	 */
	@Override
	public List<GenTable> selectDbTableList(GenTable genTable) {
		Page<Object> page = PageHelper.getLocalPage();
		PageHelper.clearPage();
		int pageNum = 1;
		int pageSize = Integer.MAX_VALUE;
		if(page != null) {
			pageNum = page.getPageNum();
			pageSize = page.getPageSize();
		}
		
		String beginTimeStr = (String)genTable.getParams().get("beginTime");
		String endTimeStr = (String)genTable.getParams().get("endTime");
		Date beginTime, endTime;
		if(StringUtils.isNotBlank(beginTimeStr)) {
			beginTime = DateUtil.parseDate(beginTimeStr);
		} else {
			beginTime = null;
		}
		if(StringUtils.isNotBlank(endTimeStr)) {
			endTime = DateUtil.endOfDay(DateUtil.parseDate(endTimeStr));
		} else {
			endTime = null;
		}
		
		TableFactory factory = TableFactory.newInstance(dataSource);
		Page<Table> tableList = (Page<Table>) factory.getSimpleTableInfo(genTable.getTableName(), genTable.getTableComment(), beginTime, endTime, pageNum, pageSize);
		Page<GenTable> getTablePageList = new Page<>();
		getTablePageList.setTotal(tableList.getTotal());
		getTablePageList.addAll(tableList.stream().map(item -> {
			GenTable tmpTable = new GenTable();
			tmpTable.setTableComment(item.getTableComments());
			tmpTable.setTableName(item.getJdbcName());
			tmpTable.setCreateTime(item.getCreateTime());
			tmpTable.setUpdateTime(item.getUpdateTime());
			tmpTable.setClassName(item.getClassName());
			return tmpTable;
		}).collect(Collectors.toList()));
		
		return getTablePageList;
	}

	/**
	 * 查询所有表信息
	 * 
	 * @return 表信息集合
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<GenTable> selectGenTableAll() {
		List<GenTable> tableList = genTableMapper.findAll();
		List<GenTableColumn> columnList = genTableColumnMapper.findAllAndSort(Sort.by(GenTableColumn::getSort));
		for(GenTable table : tableList) {
			table.setColumns(columnList.stream().filter(item -> Objects.equal(table.getTableId(), item.getTableId())).collect(Collectors.toList()));
		}
		return tableList;
	}

	/**
	 * 修改业务
	 * 
	 * @param genTable 业务信息
	 * @return 结果
	 */
	@Override
	@Transactional
	public void updateGenTable(GenTable genTable) {
		String options = JSON.toJSONString(genTable.getParams());
		genTable.setOptions(options);
		int row = genTableMapper.updateById(genTable);
		if (row > 0) {
			for (GenTableColumn cenTableColumn : genTable.getColumns()) {
				genTableColumnMapper.updateById(cenTableColumn);
			}
		}
	}

	/**
	 * 删除业务对象
	 * 
	 * @param tableIds 需要删除的数据ID
	 * @return 结果
	 */
	@Override
	@Transactional
	public void deleteGenTableByIds(Long[] tableIds) {
		genTableMapper.deleteByIds(Arrays.asList(tableIds));
		genTableColumnMapper.deleteByTableIdIn(tableIds);
	}

	/**
	 * 导入表结构
	 * 
	 * @param tableList 导入表列表
	 */
	@Override
	@Transactional
	public void importGenTable(String[] tableNames) {
		String operName = ""; // UserUtils.getCurrentUserName();
		TableFactory tableFactory = TableFactory.newInstance(dataSource);
		for (String tableName : tableNames) {
			Table table = tableFactory.getTable(tableName);
			genTable(table, operName);
		}
	}

	private void genTable(Table table, String operName) {
		GenTable genTable = new GenTable();
		genTable.setClassName(table.getClassName());
		genTable.setCreateBy(operName);
		genTable.setCreateTime(new Date());
		genTable.setTableName(table.getJdbcName());
		genTable.setTableComment(table.getTableComments());
		genTable.setPackageName(GenConfig.getPackageName());
		genTable.setModuleName(GenConfig.getModuleName());
		genTable.setBusinessName(table.getFieldName());
		genTable.setFunctionName(table.getTableComments());
		genTable.setFunctionAuthor(GenConfig.getAuthor());
		genTable.setTplCategory(GenConstants.TPL_CRUD);

		genTableMapper.insert(genTable);

		Collection<Column> columns = table.getColumns();
		List<GenTableColumn> genTableColumns = new ArrayList<>(columns.size());
		genTable.setColumns(genTableColumns);
		int sort = 0;
		for (Column column : columns) {
			GenTableColumn genTableColumn = new GenTableColumn();
			genTableColumns.add(genTableColumn);

			String columnName = column.getJdbcName();
			genTableColumn.setColumnComment(column.getColumnComments());
			genTableColumn.setColumnName(column.getJdbcName());
			genTableColumn.setColumnType(column.getJdbcSqlTypeName()); // TODO
			genTableColumn.setDictType("");
			genTableColumn.setHtmlType(getHtmlType(column));
			genTableColumn.setIsEdit(
					!StringUtils.containsAny(columnName, GenConstants.COLUMNNAME_NOT_EDIT) && !column.isPk() ? GenConstants.YES
							: GenConstants.NO);
			genTableColumn.setIsIncrement(column.isAutoIncrement() ? GenConstants.YES : GenConstants.NO);
			genTableColumn.setIsInsert(GenConstants.REQUIRE);
			genTableColumn.setIsList(
					!StringUtils.containsAny(columnName, GenConstants.COLUMNNAME_NOT_LIST) && !column.isPk() ? GenConstants.YES
							: GenConstants.NO);
			genTableColumn.setIsPk(column.isPk() ? GenConstants.YES : GenConstants.NO);
			genTableColumn.setIsQuery(
					!StringUtils.containsAny(columnName, GenConstants.COLUMNNAME_NOT_QUERY) && !column.isPk() ? GenConstants.YES
							: GenConstants.NO);
			genTableColumn.setIsRequired(column.isNullable() ? GenConstants.NO : GenConstants.YES);
			genTableColumn.setJavaField(column.getJavaFieldName());
			genTableColumn.setQueryType(getQueryType(column));
			genTableColumn.setSort(++sort);
			genTableColumn.setTableId(genTable.getTableId());
			genTableColumn.setJavaType(column.getSimpleJavaType());
			genTableColumn.setJdbcType(column.getJdbcType());
			genTableColumn.setColumnLength(column.getSize());

			genTableColumnMapper.insert(genTableColumn);
		}
	}

	private String getHtmlType(Column column) {
		if (column.getIsStringColumn()) {
			if (column.getSize() >= 500 || Types.LONGNVARCHAR == column.getJdbcType()
					|| Types.LONGVARCHAR == column.getJdbcType()) {
				return GenConstants.HTML_TEXTAREA;
			}
			return GenConstants.HTML_INPUT;
		} else if (column.getIsDateTimeColumn()) {
			return GenConstants.HTML_DATETIME;
		}
		return GenConstants.HTML_INPUT;
	}
	
	private String getQueryType(Column column) {
		if(StringUtils.endsWithIgnoreCase(column.getJdbcName(), "name")) {
			return GenConstants.QUERY_LIKE;
		}
		return "EQ";
	}

	/**
	 * 预览代码
	 * 
	 * @param tableId 表编号
	 * @return 预览数据列表
	 */
	@Override
	public Map<String, String> previewCode(Long tableId) {
		Map<String, String> dataMap = new LinkedHashMap<>();
		// 查询表信息
		GenTable table = this.selectGenTableById(tableId);
		// 设置主子表信息
		setSubTable(table);
		// 设置主键列信息
		setPkColumn(table);

		Map<String, Object> templateParams = EnjoyUtils.prepareTemplateParams(table);

		// 获取模板列表
		List<String> templates = EnjoyUtils.getTemplateList(table.getTplCategory());
		for (String template : templates) {
			// 渲染模板
			StringWriter sw = new StringWriter();
			Template tpl = engine.getTemplate(template);
			tpl.render(templateParams, sw);

			dataMap.put(template, sw.toString());
		}

		return dataMap;
	}

	/**
	 * 生成代码（下载方式）
	 * 
	 * @param tableName 表名称
	 * @return 数据
	 */
	@Override
	public byte[] downloadCode(String tableName) {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		ZipOutputStream zip = new ZipOutputStream(outputStream);
		generatorCode(tableName, zip);
		IOUtils.closeQuietly(zip);
		return outputStream.toByteArray();
	}

	/**
	 * 生成代码（自定义路径）
	 * 
	 * @param tableName 表名称
	 */
	@Override
	public void generatorCode(String tableName) {
		// 查询表信息
		
		GenTable table = getByTableName(tableName);
		// 设置主子表信息
		setSubTable(table);
		// 设置主键列信息
		setPkColumn(table);

		Map<String, Object> templateParams = EnjoyUtils.prepareTemplateParams(table);

		// 获取模板列表
		List<String> templates = EnjoyUtils.getTemplateList(table.getTplCategory());
		for (String template : templates) {
			if (!StringUtils.containsAny(template, "sql.ej", "api.js.ej", "index.vue.ej", "index-tree.vue.ej")) {
				// 渲染模板
				StringWriter sw = new StringWriter();
				Template tpl = engine.getTemplate(template);
				tpl.render(templateParams, sw);
				try {
					String path = getGenPath(table, template);
					FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8);
				} catch (IOException e) {
					throw new ValidateException("渲染模板失败，表名：" + table.getTableName());
				}
			}
		}
	}
	
	private GenTable getByTableName(String tableName) {
		return this.selectGenTableById(genTableMapper.findByTableName(tableName).orElseThrow(() -> new ValidateException("构建表不存在：" + tableName)).getTableId());
	}

	/**
	 * 同步数据库
	 * 
	 * @param tableName 表名称
	 */
	@Override
	@Transactional
	public void synchDb(String tableName) {
		GenTable genTable = getByTableName(tableName);
		List<GenTableColumn> tableColumns = genTable.getColumns();
		List<String> tableColumnNames = tableColumns.stream().map(GenTableColumn::getColumnName)
				.collect(Collectors.toList());

		TableFactory tableFactory = TableFactory.newInstance(dataSource);
		Table table = tableFactory.getTable(tableName);
		List<GenTableColumn> dbTableColumns = genTableColumns(table, genTable);
		if (StringUtils.isEmpty(dbTableColumns)) {
			throw new ValidateException("同步数据失败，原表结构不存在");
		}
		List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName)
				.collect(Collectors.toList());

		dbTableColumns.forEach(column -> {
			if (!tableColumnNames.contains(column.getColumnName())) {
				GenUtils.initColumnField(column, genTable);
				genTableColumnMapper.insert(column);
			}
		});

		List<GenTableColumn> delColumns = tableColumns.stream()
				.filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
		if (StringUtils.isNotEmpty(delColumns)) {
			genTableColumnMapper.deleteByIds(delColumns.stream().map(GenTableColumn::getColumnId).collect(Collectors.toList()));
		}
	}
	
	private List<GenTableColumn> genTableColumns(Table table, GenTable genTable){
		Collection<Column> columns = table.getColumns();
		List<GenTableColumn> genTableColumns = new ArrayList<>(columns.size());
		
		int sort = 0;
		for (Column column : columns) {
			GenTableColumn genTableColumn = new GenTableColumn();
			genTableColumns.add(genTableColumn);

			String columnName = column.getJdbcName();
			genTableColumn.setColumnComment(column.getColumnComments());
			genTableColumn.setColumnName(column.getJdbcName());
			genTableColumn.setColumnType(column.getJdbcSqlTypeName()); // TODO
			genTableColumn.setDictType("");
			genTableColumn.setHtmlType(getHtmlType(column));
			genTableColumn.setIsEdit(
					!StringUtils.containsAny(columnName, GenConstants.COLUMNNAME_NOT_EDIT) && !column.isPk() ? GenConstants.YES
							: GenConstants.NO);
			genTableColumn.setIsIncrement(column.isAutoIncrement() ? GenConstants.YES : GenConstants.NO);
			genTableColumn.setIsInsert(GenConstants.REQUIRE);
			genTableColumn.setIsList(
					!StringUtils.containsAny(columnName, GenConstants.COLUMNNAME_NOT_LIST) && !column.isPk() ? GenConstants.YES
							: GenConstants.NO);
			genTableColumn.setIsPk(column.isPk() ? GenConstants.YES : GenConstants.NO);
			genTableColumn.setIsQuery(
					!StringUtils.containsAny(columnName, GenConstants.COLUMNNAME_NOT_QUERY) && !column.isPk() ? GenConstants.YES
							: GenConstants.NO);
			genTableColumn.setIsRequired(column.isNullable() ? GenConstants.NO : GenConstants.YES);
			genTableColumn.setJavaField(column.getJavaFieldName());
			genTableColumn.setQueryType(getQueryType(column));
			genTableColumn.setSort(++sort);
			genTableColumn.setTableId(genTable.getTableId());
			genTableColumn.setJavaType(column.getSimpleJavaType());
			genTableColumn.setJdbcType(column.getJdbcType());
			genTableColumn.setColumnLength(column.getSize());

			genTableColumnMapper.insert(genTableColumn);
		}
		
		return genTableColumns;
	}

	/**
	 * 批量生成代码（下载方式）
	 * 
	 * @param tableNames 表数组
	 * @return 数据
	 */
	@Override
	public byte[] downloadCode(String[] tableNames) {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		ZipOutputStream zip = new ZipOutputStream(outputStream);
		for (String tableName : tableNames) {
			generatorCode(tableName, zip);
		}
		IOUtils.closeQuietly(zip);
		return outputStream.toByteArray();
	}

	/**
	 * 查询表信息并生成代码
	 */
	private void generatorCode(String tableName, ZipOutputStream zip) {
		// 查询表信息
		GenTable table = getByTableName(tableName);;
		// 设置主子表信息
		setSubTable(table);
		// 设置主键列信息
		setPkColumn(table);

		Map<String, Object> templateParams = EnjoyUtils.prepareTemplateParams(table);

		// 获取模板列表
		List<String> templates = EnjoyUtils.getTemplateList(table.getTplCategory());
		for (String template : templates) {
			// 渲染模板
			StringWriter sw = new StringWriter();
			Template tpl = engine.getTemplate(template);
			tpl.render(templateParams, sw);
			try {
				// 添加到zip
				zip.putNextEntry(new ZipEntry(EnjoyUtils.getFileName(template, table)));
				IOUtils.write(sw.toString(), zip, Constants.UTF8);
				IOUtils.closeQuietly(sw);
				zip.flush();
				zip.closeEntry();
			} catch (IOException e) {
				log.error("渲染模板失败，表名：" + table.getTableName(), e);
			}
		}
	}

	/**
	 * 修改保存参数校验
	 * 
	 * @param genTable 业务信息
	 */
	@Override
	public void validateEdit(GenTable genTable) {
		if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
			String options = JSON.toJSONString(genTable.getParams());
			JSONObject paramsObj = JSON.parseObject(options);
			if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) {
				throw new ValidateException("树编码字段不能为空");
			} else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) {
				throw new ValidateException("树父编码字段不能为空");
			} else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) {
				throw new ValidateException("树名称字段不能为空");
			} else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) {
				if (StringUtils.isEmpty(genTable.getSubTableName())) {
					throw new ValidateException("关联子表的表名不能为空");
				} else if (StringUtils.isEmpty(genTable.getSubTableFkName())) {
					throw new ValidateException("子表关联的外键名不能为空");
				}
			}
		}
	}

	/**
	 * 设置主键列信息
	 * 
	 * @param table 业务表信息
	 */
	private void setPkColumn(GenTable table) {
		for (GenTableColumn column : table.getColumns()) {
			if (column.isPk()) {
				table.setPkColumn(column);
				break;
			}
		}
		if (StringUtils.isNull(table.getPkColumn())) {
			table.setPkColumn(table.getColumns().get(0));
		}
		if (GenConstants.TPL_SUB.equals(table.getTplCategory())) {
			for (GenTableColumn column : table.getSubTable().getColumns()) {
				if (column.isPk()) {
					table.getSubTable().setPkColumn(column);
					break;
				}
			}
			if (StringUtils.isNull(table.getSubTable().getPkColumn())) {
				table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
			}
		}
	}

	/**
	 * 设置主子表信息
	 * 
	 * @param table 业务表信息
	 */
	public void setSubTable(GenTable table) {
		String subTableName = table.getSubTableName();
		if (StringUtils.isNotEmpty(subTableName)) {
			table.setSubTable(getByTableName(subTableName));
		}
	}

	/**
	 * 设置代码生成其他选项值
	 * 
	 * @param genTable 设置后的生成对象
	 */
	public void setTableFromOptions(GenTable genTable) {
		JSONObject paramsObj = JSON.parseObject(genTable.getOptions());
		if (StringUtils.isNotNull(paramsObj)) {
			String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
			String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
			String treeName = paramsObj.getString(GenConstants.TREE_NAME);
			String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID);
			String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);

			genTable.setTreeCode(treeCode);
			genTable.setTreeParentCode(treeParentCode);
			genTable.setTreeName(treeName);
			genTable.setParentMenuId(parentMenuId);
			genTable.setParentMenuName(parentMenuName);
		}
	}

	/**
	 * 获取代码生成地址
	 * 
	 * @param table    业务表信息
	 * @param template 模板文件路径
	 * @return 生成地址
	 */
	public static String getGenPath(GenTable table, String template) {
		String genPath = table.getGenPath();
		if (StringUtils.equals(genPath, "/")) {
			return System.getProperty("user.dir") + File.separator + "src" + File.separator
					+ EnjoyUtils.getFileName(template, table);
		}
		return genPath + File.separator + EnjoyUtils.getFileName(template, table);
	}
}